/*
	Programação Concorrente
	Trabalho 3
	Turma A

	Adilson Abreu Couto Rapozo Junior ( n. USP: 6402336 )
	Luiz Alfredo Bertoni ( n. USP: 6418834 )
	Rodrigo Fernandes da Costa ( n. USP: 6793073 )
*/

#include "main.h"

int main(int argc, char *argv[]) {

    int rank, size;
    double t1=.0,t2=.0,t3=.0,t4=.0;
      
    int rc = MPI_Init(&argc, &argv);
    
    if(rc != MPI_SUCCESS)
        return 1;
    
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
        
    /*loading system and getting reading time*/
    if (rank == 0)
    	t1 = MPI_Wtime();
    strut * system = loadStrut(argv[1]);
    if (rank == 0)
    	t2 = MPI_Wtime();
    
    double * k0 = malloc(system->J_ORDER*sizeof(double));
    double * k1;
    int i;
    
    MPI_Status status;
    int tag = 11;
    int pcontinue = 1;
    int pbegin, pend;
        
    if(rank == 0) {

	t3 = MPI_Wtime();
                
        int iterations;
        int j, w;
        
        double error;
        double aux;
        
        double * k_slave = malloc(system->J_ORDER*sizeof(double));
        k1 = malloc(system->J_ORDER*sizeof(double));
       
        for(i = 0; i < system->J_ORDER; i++)
            k1[i] = 0.0f;
        
        for(i = 0;;i++) {
            
            
            MPI_Bcast(k1, system->J_ORDER, MPI_DOUBLE, 0, MPI_COMM_WORLD);
            
            for(w = 0; w < system->J_ORDER; w++)
                k0[w] = k1[w];
            
            for(j = 1; j < size; j++) {
                
                pbegin = (j - 1)*(system->J_ORDER/(size - 1));
                pend = (j == size - 1) ? system->J_ORDER : (j)*(system->J_ORDER/(size - 1));
                
                MPI_Recv(k_slave, (pend - pbegin), MPI_DOUBLE, j, tag, MPI_COMM_WORLD, &status);
                
                for(w = pbegin; w < pend; w++)
                    k1[w] = k_slave[w - pbegin];    
            }
            
            if((error = jacobiError(k1, k0, system->J_ORDER)) < system->J_ERROR) {
                pcontinue = 0;
                MPI_Bcast(&pcontinue, 1, MPI_INT, 0, MPI_COMM_WORLD);
                t4 = MPI_Wtime();
                break;
            } else if(i == (system->J_ITE_MAX - 1)) { 
                
                pcontinue = 0;
                MPI_Bcast(&pcontinue, 1, MPI_INT, 0, MPI_COMM_WORLD);
                i++;
		t4 = MPI_Wtime();
                break;
            } else {

                pcontinue = 1;
                MPI_Bcast(&pcontinue, 1, MPI_INT, 0, MPI_COMM_WORLD);
            }
        }
        
        iterations = i;
        
        aux = 0.0f;
        
        for(i = 0; i < system->J_ORDER; i++)
            aux += system->matrix[system->J_ROW_TEST][i] * k1[i];
        
        if((aux <= system->matrix[system->J_ROW_TEST][system->J_ORDER] + error) && (aux >= system->matrix[system->J_ROW_TEST][system->J_ORDER] - error)) {
            printf("---------------------------------------\n");
            printf("Iterations: %d\n", iterations);
            printf("RowTest: %d => [%.6lf] =? %.6lf\n", system->J_ROW_TEST, aux, system->matrix[system->J_ROW_TEST][system->J_ORDER]);
            printf("---------------------------------------\n");
        }

        free(k1);
        free(k_slave);
        
    }
    
    else {
        
        pbegin = (rank - 1)*(system->J_ORDER/(size - 1));
        pend = (rank == size - 1) ? system->J_ORDER : (rank)*(system->J_ORDER/(size - 1));
        
        k1 = (double *)malloc((pend - pbegin)*sizeof(double));
     
        double * k_aux = &k1[(pend + pbegin)/2 - pbegin];
        
       while(pcontinue) {
            
            MPI_Bcast(k0, system->J_ORDER, MPI_DOUBLE, 0, MPI_COMM_WORLD);
            
            
            #pragma omp parallel
            {
                
                #pragma omp sections 
                {
                    #pragma omp section
                    {
                        jacobi(system, k1, k0, pbegin, (pend + pbegin)/2);
                    }
                    
                    #pragma omp section
                    {
                        jacobi(system, k_aux, k0, (pend + pbegin)/2, pend);
                    }
                }
            }
            

            MPI_Send(k1, (pend - pbegin), MPI_DOUBLE, 0, tag, MPI_COMM_WORLD);
            
            
            MPI_Bcast(&pend, 1, MPI_INT, 0, MPI_COMM_WORLD);
            
        }
       
        free(k1);
        
    }

    
    free(k0);

    /* Desaloca sistema da memoria */
    cleansys(system); 
    /* Termina MPI */
    MPI_Finalize();
    if( rank == 0){
    	printf("\nTempo de Leitura: %.3lf\n",t2 - t1);
    	printf("Tempo de Execução: %.3lf\n\n",fabs(t3 - t4));
    }
    return 0;
}
